/* Une partie de ce code est Copyright Taghrid Asfour */

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include "../structure.h"

#define SERVER_PORT 1500
#define CLIENT_PORT 1234
#define MAX_MSG 80

void init_messsage(paquet* mesg_)
{
	// construire un message en y insérant le numPaquet et les données
    if(mesg_==NULL) {
    	mesg_= malloc(sizeof(paquet));
    }

    //On insert le numéro de block
    mesg_->numBlock=0;

    //On insert le numéro de séquence
    mesg_->numPaquet=0; 

    //Si le paquet est un ack il vaut 1
    mesg_->ack=0;

    //Si le paquet est un ack de type flux
    mesg_->flux=0;

    //Pour la gestion de congestion
    mesg_->flag=0;

	//On init la data a vide
	int y = 0 ;
	for(y=0;y<DATA;y++)
	{
		mesg_->data[y]=' ';
	}
}

void ecriture_fichier(paquet tab[]){
	//On ouvre le fichier
	FILE * fic ;
    fic = fopen("out.txt", "a+") ;
    if(fic == NULL)
    {
            printf("Impossible d'ouvrir le fichier !");
            exit(1);
    }

	//On ecrit dans le fichier
	int i=0 ;
    for (i=0;i<8;i++)
    {
            fputs(tab[i].data, fic);
    }

	//On ferme le fichier
	fclose(fic);
}

void init_file()
{
	//On ouvre le fichier
	FILE * fic ;
    fic = fopen("out.txt", "w") ;
    if(fic == NULL){
            printf("Impossible d'ouvrir le fichier !");
            exit(1);
    }
        
	//On ferme le fichier
	fclose(fic);
}

int main (int argc, char *argv[])
{
	
	//CtoS : communication Client => Serveur
	//StoC : communication Serveur => Client
	int sd_reception, n, sd_envoi, CtoS_senderLen;
	struct sockaddr_in CtoS_sender, CtoS_receiver, StoC_sender, StoC_receiver;
	
	// préparation pour la socket de reception
	paquet msg_recu, msg_temp, msg_ack, msg_vide, a_envoyer ;
	// Create and bind socket 
	if ((sd_reception = socket(PF_INET,SOCK_DGRAM, 0)) < 0)
	{
		perror(argv[0]);
		exit(1);
	}
	CtoS_receiver.sin_family = AF_INET;
	CtoS_receiver.sin_addr.s_addr = htonl(INADDR_ANY);
	CtoS_receiver.sin_port = htons(SERVER_PORT);
	if (bind(sd_reception, (struct sockaddr *)&CtoS_receiver, sizeof(CtoS_receiver)) < 0)
	{
		perror(argv[0]);
		exit(1);
	}
	
	// préparation pour la sockets d'envoi

	/* Create and bind socket */
	if ((sd_envoi = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
	{
		perror(argv[0]);
		exit(1);
	}

	StoC_sender.sin_family = AF_INET;
	StoC_sender.sin_addr.s_addr = htonl(INADDR_ANY);
	StoC_sender.sin_port = htons(0);
	if (bind(sd_envoi,(struct sockaddr *)&StoC_sender, sizeof(StoC_sender)) < 0)
	{
		perror(argv[0]);
		exit(1);
	}

	/* Fill server address structure */
	StoC_receiver.sin_family = AF_INET;
	if (inet_aton("127.0.0.1", &(StoC_receiver.sin_addr))==0)
	{
		printf("%s: Invalid IP address format <%s>\n", argv[0], argv[1]);
		exit(1);
	}

	StoC_receiver.sin_port = htons(CLIENT_PORT);

	//Variables
	int block_courant=0, cpt=0;
	paquet tab[8];
	
	// on initialise le fichier de sortie
	init_file();

	//Boucle d'écoute infinie avec traitement sérialisé des paquets
	for (;;)
	{
		CtoS_senderLen = sizeof(CtoS_sender);
		n = recvfrom(sd_reception, &msg_recu, MAX_MSG, 0,(struct sockaddr *)&CtoS_sender, (socklen_t*)&CtoS_senderLen);
		if (n<0) 
		{
			perror(argv[0]);
			 exit(0);
		} 
		else
		{
			
			// on vérifie en premier lieu si c'est un paquet de fin de transmission
			// si oui, on écrit le paquet en cours et on réinitialise le serveur
			if((msg_recu.ack==0)&&(msg_recu.flux==1)&&(msg_recu.flag==1))
			{
				// fin envoi. il faut remettre le serveur en écoute
				ecriture_fichier(tab) ;
				printf("#################################################\n") ;
				printf("Fin d'envoi : en attente de nouvelle connexion...\n") ;
				printf("#################################################\n") ;
				block_courant=0, cpt=0 ;
				init_messsage(&msg_vide);
				int x ; for (x=0;x<8;x++) tab[x] = msg_vide;
			}
			else
			{
				printf("%s: received from %s (n° de block : %d, n° paquet : %d) :\n%s",argv[0], inet_ntoa(CtoS_sender.sin_addr),msg_recu.numBlock,msg_recu.numPaquet,msg_recu.data);
				printf("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
			
				//Si le numBlock du paquet reçu correspond au block courant, on l'enregistre dans la table
				// et on passe directement à la réception du suivant
				if (msg_recu.numBlock == block_courant)
				{
					tab[msg_recu.numPaquet] = msg_recu;
					cpt++;
				}
			
				// on est arrivé à la fin d'un block.
				// on stocke le paquet qui vient d'arriver (le premier du block suivant)
				//on choisit si on envoie un ACK positif ou negatif
				else
				{ 
					msg_temp = msg_recu; // on stocke le msg du block suivant
					init_messsage(&msg_ack);
				
					// TEST d'intégralité du block
					if(cpt<8)
					{
						// construction reponse qui va pas (ack=1, flag=1 : ack negatif)
						msg_ack.ack=1;
						msg_ack.flag=1;
						printf("Construction ACK negatif\n");
					}
					else
					{
						// envoi ack tt va bien (ACK positif)
						msg_ack.numBlock=block_courant;
						msg_ack.ack=1;
						printf("Construction ACK positif\n");
					}// fin else

					// on envoie l'ACK qu'on vient de construire
					if (sendto(sd_envoi, &msg_ack, sizeof(msg_ack), 0,(struct sockaddr *)&StoC_receiver, sizeof(StoC_receiver)) < 0)
					{
						perror(argv[0]); exit(1);
					}
					// on écrit dans le fichier, même si le block n'est pas complet
					ecriture_fichier(tab);

					// reinitialiser la table
					init_messsage(&msg_vide);
					int z ; for (z=0;z<8;z++) tab[z] = msg_vide;
				
					// ecrire le paquet temp dans la nouvelle table
					tab[msg_temp.numPaquet] = msg_temp;
				
					// on met cpt à 1 (on a en effet stocké le premier paquet du block suivant)
					cpt=1;
					// incrementer block courant
					block_courant++;
				}	
			}
		}
	}

	close(sd_envoi);
	close(sd_reception);
}


